package org.andengine.opengl.texture.bitmap;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;

import org.andengine.opengl.texture.ITextureStateListener;
import org.andengine.opengl.texture.PixelFormat;
import org.andengine.opengl.texture.Texture;
import org.andengine.opengl.texture.TextureManager;
import org.andengine.opengl.texture.TextureOptions;
import org.andengine.opengl.util.GLState;
import org.andengine.util.StreamUtils;
import org.andengine.util.adt.io.in.IInputStreamOpener;
import org.andengine.util.exception.NullBitmapException;
import org.andengine.util.math.MathUtils;

import java.io.IOException;
import java.io.InputStream;

/**
 * (c) Zynga 2011
 *
 * @author Nicolas Gramlich <ngramlich@zynga.com>
 * @since 16:16:25 - 30.07.2011
 */
public class BitmapTexture extends Texture {
    // ===========================================================
    // Constants
    // ===========================================================

    // ===========================================================
    // Fields
    // ===========================================================

    private final int mWidth;
    private final int mHeight;
    private final IInputStreamOpener mInputStreamOpener;
    private final BitmapTextureFormat mBitmapTextureFormat;

    // ===========================================================
    // Constructors
    // ===========================================================

    public BitmapTexture(final TextureManager pTextureManager, final IInputStreamOpener pInputStreamOpener) throws IOException {
        this(pTextureManager, pInputStreamOpener, BitmapTextureFormat.RGBA_8888, TextureOptions.DEFAULT, null);
    }

    public BitmapTexture(final TextureManager pTextureManager, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat) throws IOException {
        this(pTextureManager, pInputStreamOpener, pBitmapTextureFormat, TextureOptions.DEFAULT, null);
    }

    public BitmapTexture(final TextureManager pTextureManager, final IInputStreamOpener pInputStreamOpener, final TextureOptions pTextureOptions) throws IOException {
        this(pTextureManager, pInputStreamOpener, BitmapTextureFormat.RGBA_8888, pTextureOptions, null);
    }

    public BitmapTexture(final TextureManager pTextureManager, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions) throws IOException {
        this(pTextureManager, pInputStreamOpener, pBitmapTextureFormat, pTextureOptions, null);
    }

    public BitmapTexture(final TextureManager pTextureManager, final IInputStreamOpener pInputStreamOpener, final BitmapTextureFormat pBitmapTextureFormat, final TextureOptions pTextureOptions, final ITextureStateListener pTextureStateListener) throws IOException {
        super(pTextureManager, pBitmapTextureFormat.getPixelFormat(), pTextureOptions, pTextureStateListener);

        this.mInputStreamOpener = pInputStreamOpener;
        this.mBitmapTextureFormat = pBitmapTextureFormat;

        final BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        decodeOptions.inJustDecodeBounds = true;

        final InputStream in = null;
        try {
            BitmapFactory.decodeStream(pInputStreamOpener.open(), null, decodeOptions);
        } finally {
            StreamUtils.close(in);
        }

        this.mWidth = decodeOptions.outWidth;
        this.mHeight = decodeOptions.outHeight;
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    @Override
    public int getWidth() {
        return this.mWidth;
    }

    @Override
    public int getHeight() {
        return this.mHeight;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    protected void writeTextureToHardware(final GLState pGLState) throws IOException {
        final Config bitmapConfig = this.mBitmapTextureFormat.getBitmapConfig();
        final Bitmap bitmap = this.onGetBitmap(bitmapConfig);

        if (bitmap == null) {
            throw new NullBitmapException("Caused by: '" + this.toString() + "'.");
        }

        final boolean useDefaultAlignment = MathUtils.isPowerOfTwo(bitmap.getWidth()) && MathUtils.isPowerOfTwo(bitmap.getHeight()) && (this.mPixelFormat == PixelFormat.RGBA_8888);
        if (!useDefaultAlignment) {
            /* Adjust unpack alignment. */
            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
        }

        final boolean preMultipyAlpha = this.mTextureOptions.mPreMultiplyAlpha;
        if (preMultipyAlpha) {
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        } else {
            pGLState.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0, this.mPixelFormat);
        }

        if (!useDefaultAlignment) {
            /* Restore default unpack alignment. */
            GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, GLState.GL_UNPACK_ALIGNMENT_DEFAULT);
        }

        bitmap.recycle();
    }

    // ===========================================================
    // Methods
    // ===========================================================

    protected Bitmap onGetBitmap(final Config pBitmapConfig) throws IOException {
        final BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
        decodeOptions.inPreferredConfig = pBitmapConfig;

        return BitmapFactory.decodeStream(this.mInputStreamOpener.open(), null, decodeOptions);
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
